﻿#include "litedb.h"
#include <time.h>
#include <QApplication>
#include <QDir>
#include <QCryptographicHash>
#include "litedbtool.h"
#include "uitools.h"

LiteDb::LiteDb()
{

}

bool LiteDb::beginTransaction()
{
    if(!ltdb_user.isOpen())
        return false;
    return ltdb_user.transaction();
}

bool LiteDb::commit()
{
    if(!ltdb_user.isOpen())
        return false;
    return ltdb_user.commit();
}

bool LiteDb::rollback()
{
    if(!ltdb_user.isOpen())
        return false;
    return ltdb_user.rollback();
}

void LiteDb::init_user_db(const QString &my_id)
{
    if(!myid_.isEmpty() && myid_!=my_id)
    {
        close_user_db();
    }
    myid_ = my_id;

    open_user_db();
}

bool LiteDb::open_user_db()
{
    if(ltdb_user.isOpen())
        return true;
    if(myid_.isEmpty())
        return false;
    ltdb_user = QSqlDatabase::addDatabase("QSQLITE", "litedb_user");

    ltdb_user.setDatabaseName(userDataDir(myid_) + '/' + myid_ +".db");
    bool ret = ltdb_user.open();
    if(ret)
    {
        create_table(ltdb_user,gImContactTbl);
        create_table(ltdb_user,gChatRoomTbl);
        create_table(ltdb_user,gSessionTbl);
    }
    return ret;
}

void LiteDb::close_user_db()
{
    if (ltdb_user.isOpen())
        ltdb_user.close();
    myid_.clear();
    gChatRecordTbl.clear();
}

QMap<qint64, QVariant> LiteDb::query_contact()
{
    QMap<qint64, QVariant> result;
    if(!open_user_db()) return result;

    QSqlQuery query(ltdb_user);

    if(!query_table(query, gImContactTbl))
        return result;

    while (query.next())
    {
        QSqlRecord record = query.record();
        if(qint64 uid = record.value(gImContactTbl.uid).toLongLong())
        {
            auto info = record.value(gImContactTbl.info).toByteArray();
            QDataStream ds(info);
            QVariantHash row;
            ds>>row;
            if(!row.isEmpty())
                result[uid] = row;
        }
    }
    return result;
}

void LiteDb::update_contact(const QVariantList& vars)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    if(prepareInsert(query, gImContactTbl, vars))
        debug_exec(query);
    else
        log_debug()<<query.lastError().text();
}

void LiteDb::delete_contact(const qint64 uid)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    QString sql="delete from "+gImContactTbl.tableName+" where "+gImContactTbl.uid+" = "+QString::number(uid);

    debug_exec(query,sql);
}

QStringList LiteDb::contact_fields()
{
    QStringList fields;
    foreach (const DatabaseField &field, gImContactTbl.fields) {
        fields.append(field.fieldName);
    }
    return fields;
}

qint64 LiteDb::query_room_updatetime()
{
    qint64 time = 0;
    if(!open_user_db())
        return time;

    QSqlQuery query(ltdb_user);
    QString statement = "select " + gChatRoomTbl.updtime + " from " + gChatRoomTbl.tableName + " ORDER BY " + gChatRoomTbl.updtime + " DESC LIMIT 1 ";
    if(!debug_exec(query,statement))
        return time;
    if(query.next())
    {
        QSqlRecord record = query.record();
        return record.value(0).toLongLong();
    }
    return time;
}

QMap<qint64, QVariant> LiteDb::query_rooms()
{
    QMap<qint64, QVariant> result;
    if(!open_user_db())
        return result;

    QSqlQuery query(ltdb_user);

    if(!query_table(query, gChatRoomTbl.tableName, " ORDER BY "+gChatRoomTbl.updtime+" DESC"))
        return result;

    while (query.next())
    {
        QVariantMap row;
        QSqlRecord record = query.record();
        int size = record.count();
        for(int i= 0; i < size; i++)
            row[record.fieldName(i)] = record.value(i);
        if(qint64 rid = row.value("rid").toLongLong())
            result[rid] = QVariant(row);
    }
    return result;
}

void LiteDb::update_room(const QVariantList &vars)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    if(prepareInsert(query, gChatRoomTbl, vars))
        debug_exec(query);
    else
        log_debug()<<query.lastError().text();
}

void LiteDb::update_room(qint64 rid, const QVariantMap &vars)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    QString statement = "update " + gChatRoomTbl.tableName + " set ";
    bool first = true;
    foreach (const QString& key, vars.keys()) {
        if(first)
            first = false;
        else
            statement += ", ";
        statement += key + " = ? ";
    }
    statement += "where " + gChatRoomTbl.rid + " = " + QString::number(rid);
    if(query.prepare(statement))
    {
        foreach (const QVariant& v, vars.values())
            query.addBindValue(v);
        debug_exec(query);
    }
    else
        log_debug()<<statement<<query.lastError().text();
}

void LiteDb::delete_room(qint64 rid)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    QString sql="delete from "+gChatRoomTbl.tableName+" where "+gChatRoomTbl.rid+" = "+QString::number(rid);

    debug_exec(query,sql);
}

QVariantList LiteDb::query_sessions()
{
    QVariantList result;
    if(!open_user_db())
        return result;

    QSqlQuery query(ltdb_user);

    if(!query_table(query, gSessionTbl.tableName, " ORDER BY "+gSessionTbl.timestamp+" DESC"))
        return result;

    while (query.next())
    {
        QVariantMap row;
        QSqlRecord record = query.record();
        int size = record.count();
        for(int i= 0; i < size; i++)
            row[record.fieldName(i)] = record.value(i);
        result << QVariant(row);
    }
    return result;
}

void LiteDb::update_session(const QChar &type, const QString &id, const QString &name, qint64 timestamp, uint style)
{
    if(!open_user_db()) return;

    if(id.isEmpty())
        return;

    QSqlQuery query(ltdb_user);

    QVariantList vars;
    vars << type + id << name << timestamp << style;

    if(prepareInsert(query, gSessionTbl, vars))
        debug_exec(query);
    else
        log_debug()<<query.lastError().text();
}

void LiteDb::delete_session(const QChar &type, const QString &id)
{
    if(!open_user_db()) return;

    QSqlQuery query(ltdb_user);

    QString sql="delete from "+gSessionTbl.tableName+" where "+gSessionTbl.idtype+" = '"+ type + id + '\'';

    debug_exec(query,sql);
}

const QByteArray LiteDb::create_mid(uint curtime, const QString &msg)
{
    return create_mid(curtime, msg.toUtf8());
}

const QByteArray LiteDb::create_mid(uint curtime, const QByteArray &msg)
{
    //generate random string
    int size = 8 / 4 * 3;
    static bool srand_init = false;
    if (!srand_init)
    {
        srand(curtime);
        srand_init = true;
    }
    QByteArray raw(size,0);
    for (int i = 0; i < size; i++)
    {
        raw[i] = rand();
    }
    std::string str = raw.toBase64(QByteArray::Base64UrlEncoding|QByteArray::OmitTrailingEquals).constData();
    size = 6;
    raw.fill(0, size);
    uint n = qrand();
    raw[0] = n;
    raw[1] = curtime>>8;
    raw[2] = curtime;
    n = QApplication::applicationPid() + qHash(msg,curtime);
    raw[3] = n>>16;
    raw[4] = n>>8;
    raw[5] = n;

    QByteArray tmp = raw.toBase64(QByteArray::Base64UrlEncoding|QByteArray::OmitTrailingEquals);
    if(tmp.size()>8)
        tmp.resize(8);
    return tmp.append(str.c_str(),8).replace('-','I').replace('_','i');
}

bool LiteDb::insert_chat_record(const QString &suffix, bool crowd, QVariantList &params)
{
    if(!open_user_db())
        return false;

    std::pair<std::map<QString,ChatRecordTbl>::iterator,bool> res = gChatRecordTbl.emplace(std::piecewise_construct, std::forward_as_tuple(suffix), std::forward_as_tuple(suffix,crowd));
    if(res.first != gChatRecordTbl.end())
    {
        if(res.second)
            create_table(ltdb_user,res.first->second);

        QSqlQuery query(ltdb_user);

        if(prepareInsert(query, res.first->second, params))
            return debug_exec(query);
        else
            log_debug()<<query.lastError().text();
    }
    return false;
}

bool LiteDb::has_chat_record(const QString &suffix)
{
    if(gChatRecordTbl.find(suffix)!=gChatRecordTbl.end())
        return true;
    QSqlQuery query(ltdb_user);
    QString sql = "select sql from sqlite_master where type='table' and name='"+ChatRecordTbl::basicName+suffix+"'";
    return debug_exec(query,sql) && query.next();
}

QStringList LiteDb::query_chat_record_tables()
{
    QStringList suffix;
    QSqlQuery query(ltdb_user);
    QString sql = "select name from sqlite_master where type='table' and name like '"+ChatRecordTbl::basicName+"%'";
    if(debug_exec(query,sql))
    {
        uint len = ChatRecordTbl::basicName.length();
        while(query.next())
        {
            QSqlRecord record = query.record();
            QString name = record.value(0).toString().remove(0,len);
            if(!name.isEmpty())
                suffix<<name;
        }
    }
    return suffix;
}

QVariantList LiteDb::query_chat_record(const QString &suffix, const QString &extra, const QString &extraEnd)
{
    QVariantList result;
    if(!open_user_db())
        return result;

    QSqlQuery query(ltdb_user);

    if(!query_table(query,ChatRecordTbl::basicName+suffix,extra+" ORDER BY "+ChatRecordTbl::time+" DESC "+extraEnd))
        return result;

    while (query.next())
    {
        QVariantList row;
        QSqlRecord record = query.record();
        int size = record.count();
        for(int i= 0; i < size; i++)
        {
            row << record.value(i);
        }
        result<<QVariant(row);
    }
    return result;
}

QVariantList LiteDb::query_chat_record_last_time(const QString &suffix)
{
    QVariantList vl;
    if(!open_user_db())
        return vl;

    QSqlQuery query(ltdb_user);
    QString statement = "select " + ChatRecordTbl::time + ", " + ChatRecordTbl::fromId +  ", " + ChatRecordTbl::plain + " from " + ChatRecordTbl::basicName+suffix + " ORDER BY "+ChatRecordTbl::time+" DESC LIMIT 1 ";
    if(!debug_exec(query,statement))
        return vl;
    if(query.next())
    {
        QSqlRecord record = query.record();
        return vl<<record.value(0)<<record.value(1)<<record.value(2);
    }
    return vl;
}

bool LiteDb::change_chat_record_step(const QString &suffix, int step, QStringList &params)
{
    if(params.isEmpty() || !open_user_db())
        return false;

    QSqlQuery query(ltdb_user);
    QString statement = "UPDATE "+ChatRecordTbl::basicName+suffix+" SET " + ChatRecordTbl::step + " = " + QString::number(step) + " WHERE (";
    for(int i = 0; i<params.size(); i++)
    {
        if(i>0)
            statement += " OR ";
        statement += ChatRecordTbl::mId + " = '" + params[i] + '\'';
    }
    statement += ')';
    return debug_exec(query,statement);
}

bool LiteDb::change_chat_record_time(const QString &suffix, qint64 time, QString &mid)
{
    if(!open_user_db())
        return false;
    QSqlQuery query(ltdb_user);
    QString statement = "UPDATE "+ChatRecordTbl::basicName+suffix+" SET " + ChatRecordTbl::time + " = " + QString::number(time)
            + " WHERE " + ChatRecordTbl::mId +  " = '" + mid + '\'';
    return debug_exec(query,statement);
}

bool LiteDb::delete_chat_record(const QString &suffix, bool drop)
{
    if(!open_user_db())
        return false;
    QSqlQuery query(ltdb_user);
    QString statement = (drop ? "DROP TABLE IF EXISTS " : "DELETE FROM ")+ChatRecordTbl::basicName+suffix;
    return debug_exec(query,statement);
}
